<?php

declare(strict_types=1);

namespace Yolo\Service;

use Hyperf\Context\ApplicationContext;
use Hyperf\Contract\OnRequestInterface;
use Hyperf\Stringable\Str;
use Hyperf\Support\Filesystem\Filesystem;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use ReflectionMethod;
use ReflectionObject;
use Swoole\Http\Request;
use Swoole\Http\Response;
use Throwable;
use Yolo\Command\Command;
use Yolo\Errors\Exceptions\InvalidBodyFormatException;
use Yolo\Errors\Exceptions\InvalidParameterException;
use Yolo\Errors\Exceptions\InvalidRPCTypeException;
use Yolo\Errors\Exceptions\MethodNotAllowedException;
use Yolo\Errors\Exceptions\RouteNotFoundException;
use Yolo\Errors\Exceptions\YoloException;
use Yolo\Log\Log;
use Yolo\Service\Annotations\RESTFul;
use Yolo\Service\Annotations\RPC;
use Yolo\Service\Annotations\Tunnel;
use Yolo\Service\Enums\RPCType;
use Yolo\Utils\Path;
use function Hyperf\Support\make;

/**
 * Service.
 * @date 2024/1/25
 */
class Service implements OnRequestInterface
{
    public function __construct()
    {
        ApplicationContext::getContainer()->get(Endpoint::class);
    }

    /**
     * @param Request $request
     * @param Response $response
     */
    public function onRequest($request, $response): void
    {
        $start = microtime(true);
        $isOk = true;

        $requestContent = $request->getContent();
        $args = json_decode($requestContent, true);

        $requestLog = [
            'subject' => 'rpc',
            'action' => 'request',
            'message' => 'ok',
            'data' => [
                'api' => ($args['ns'] ?? '') . '.' . ($args['api'] ?? ''),
                'duration' => 0,
            ]
        ];

        try {
             $result = $this->handleRequest($request, $response);
             if ($result !== false) {

                 if (is_array($result['result'])) {
                     $response->end(json_encode($result['result']));
                 } else {
                     $response->end($result['result']);
                 }
             }

        } catch (YoloException $exception) {
            $isOk = false;
            $requestLog['message'] = $exception->getMessage();
            $requestLog['data']['data'] = $exception->getFullJson();
            $response->header('Content-Type', 'application/json');
            $json = $exception->getJson();
            if (isset($json['extra']['http']['status'])) {
                $response->status($json['extra']['http']['status']);
            }
            $response->end(json_encode($json));

        } catch (Throwable $exception) {
            $isOk = false;
            $requestLog['message'] = $exception->getMessage();
            $response->header('Content-Type', 'application/json');
            $response->status(500);
            $response->end(json_encode([
                'code' => 500,
                'message' => 'Internal Server Error.',
            ]));
        } finally {

            $end = microtime(true);
            $requestLog['data']['duration'] = round(($end - $start) * 1000000);
            if (!$isOk) {
                Log::error($requestLog);
            }

            $requestLog['message'] = $isOk ? 'ok' : 'nok';
            $requestLog['data']['data'] = [];
            Log::info($requestLog);
        }
    }

    /**
     * @param Request $request
     * @param Response $response
     * @return array|false
     * @throws ContainerExceptionInterface
     * @throws InvalidBodyFormatException
     * @throws InvalidRPCTypeException
     * @throws NotFoundExceptionInterface
     * @throws RouteNotFoundException|InvalidParameterException|MethodNotAllowedException
     */
    private function handleRequest(Request $request, Response $response): bool|array
    {
        $uri = $request->server['request_uri'];
        if ($uri === '/favicon.ico') {
            $response->end('');
            return false;
        }

        $response->header('Content-Type', 'application/json');
        $method = $request->getMethod();
        if ($method === 'GET') {
            $endpoints = ApplicationContext::getContainer()->get(Endpoint::class);
            $response->end(json_encode($endpoints->getEndpoints()));
            return false;
        }

        if ($method !== 'POST') {
            throw new MethodNotAllowedException();
        }

        $requestContent = $request->getContent();
        $args = json_decode($requestContent, true);
        if (!$args) {
            throw new InvalidBodyFormatException();
        }

        $ns = $args['ns'] ?? 'RPC';
        $api = $args['api'] ?? '';

        $ns = RPCType::tryFrom($ns);
        if (!$ns) {
            throw new InvalidRPCTypeException();
        }
        $apiDetail = $this->getApiDetail($ns, $api);

        if (!$apiDetail) {
            throw new RouteNotFoundException([
                'ns' => $ns,
                'api' => $api,
            ]);
        }

        $controller = $apiDetail['invoke']['controller'];
        $action = $apiDetail['invoke']['action'];

        if (!class_exists($controller)) {
            throw new RouteNotFoundException([
                'ns' => $ns,
                'api' => $api,
            ]);
        }

        $controller = new $controller();

        // 如果有定义验证规则，则需要校验参数
        if (!empty($apiDetail['options']['validation'])) {
            $this->validateArgs(
                $ns,
                $args['body'],
                $apiDetail['options']['validation'],
                $controller->attributes ?? []
            );
        }

        if (! method_exists($controller, $action)) {
            throw new RouteNotFoundException([
                'ns' => $ns,
                'api' => $api,
            ]);
        }

        if ($ns === RPCType::RPC) { // RPC无需metadata
            $result = $controller->{$action}($args['body']);
        } else {
            $result = $controller->{$action}($args['body'], $args['metadata']);
        }

        return [
            'ns' => $ns->value,
            'api' => $api,
            'result' => $result,
        ];
//        $response->end(json_encode($result));
    }

    /**
     * @param RPCType $ns
     * @param array $body
     * @param array $rules
     * @param array $attributes
     * @return void
     * @throws ContainerExceptionInterface
     * @throws NotFoundExceptionInterface|InvalidParameterException
     */
    private function validateArgs(RPCType $ns, array $body, array $rules, array $attributes = []): void
    {
        $validateResult = [];
        if ($ns === RPCType::RESTFUL) {
            foreach (['path', 'qs', 'body'] as $type) {
                if (isset($rules[$type])) {
                    $validateResult = Validation::validate(
                        $body[$type] ?? [],
                        $rules[$type],
                        [],
                        $attributes
                    );
                    if ($validateResult) {
                        break;
                    }
                }
            }
        } else {
            $validateResult = Validation::validate(
                $body,
                $rules,
                [],
                $attributes
            );
        }

        if ($validateResult) {
            throw new InvalidParameterException([
                'traces' => $validateResult
            ]);
        }
    }

    private function getApiDetail(RPCType $ns, string $api)
    {
        $apis = ApplicationContext::getContainer()->get(Endpoint::class)->getEndpoints();
        foreach ($apis as $apiDetail) {
            if ($apiDetail['type'] === $ns->value && $apiDetail['options']['name'] === $api) {
                return $apiDetail;
            }
        }

        return null;
    }

    /**
     * 获取注解路径
     * @return array
     */
    public static function getAnnotationPaths(): array
    {
        /**
         * 当前只有command注解
         */
        return Command::getAnnotationPath();
    }
}
